import { Ionicons } from '@expo/vector-icons';
import { useLocalSearchParams, useRouter } from 'expo-router';
import { useState } from 'react';
import {
ActivityIndicator,
Pressable,
ScrollView,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import Svg, { Circle, Line, Polygon, Text as SvgText } from 'react-native-svg';
import { useJobDetail } from '../../src/hooks';
import { colors } from '../../src/theme';
import { formatThousands } from '../../src/utils';
import type { JobDimensionScore } from '../../src/types';
/* ===================== 雷达图(6 维度,SVG 绘制) ===================== */
/** 六边形雷达图:同心网格 + 轴线 + 数据多边形 + 维度标签 */
function RadarChart({
dimensions,
size = 160,
}: {
dimensions: JobDimensionScore[];
size?: number;
}) {
const cx = size / 2;
const cy = size / 2;
const count = dimensions.length;
// 数据/网格最大半径(预留标签空间)
const R = size / 2 - 36;
const labelR = R + 16;
const levels = [0.25, 0.5, 0.75, 1];
const angleFor = (i: number) => (-90 + i * (360 / count)) * (Math.PI / 180);
const pointFor = (i: number, ratio: number) => ({
x: cx + R * ratio * Math.cos(angleFor(i)),
y: cy + R * ratio * Math.sin(angleFor(i)),
});
const polygonPoints = (ratio: number) =>
dimensions
.map((_, i) => {
const p = pointFor(i, ratio);
return `${p.x},${p.y}`;
})
.join(' ');
const dataPoints = dimensions.map((d, i) => pointFor(i, d.value / 100));
return (
);
}
/* ===================== 子组件 ===================== */
/** 顶部导航:返回 + 标题 + 分享 */
function Header({ onBack }: { onBack: () => void }) {
return (
岗位详情
console.log('分享')} hitSlop={8}>
);
}
/** 分区标题:竖条装饰 + 标题 + 可选右侧链接 */
function SectionTitle({
title,
link,
onLink,
}: {
title: string;
link?: string;
onLink?: () => void;
}) {
return (
{title}
{link ? (
{link} >
) : null}
);
}
/* ===================== 页面 ===================== */
export default function JobDetailScreen() {
const { id } = useLocalSearchParams<{ id: string }>();
const router = useRouter();
const { detail, overallScore, salaryData, reviews, loading, error, refresh } = useJobDetail(id);
const [comment, setComment] = useState('');
// 错误态
if (error) {
return (
router.back()} />
加载失败,请重试
重试
);
}
// 加载态
if (loading || !detail || !overallScore || !salaryData) {
return (
);
}
return (
router.back()} />
{/* 1. 岗位基本信息 */}
{detail.positionName}
{detail.status}
浏览{formatThousands(detail.views)}
{detail.salaryMin}-{detail.salaryMax}K · {detail.salaryMonths}薪
{detail.companyName} · {detail.industry} · {detail.companySize}
{detail.companyLocation} · {detail.experience} · {detail.education}
{detail.benefits.map((item) => (
{item}
))}
{/* 2. 岗位综合评分 */}
console.log('评分说明')} />
{overallScore.totalScore}
分
{overallScore.level}
{overallScore.rank}
console.log('加入对比')} hitSlop={6}>
加入对比
{/* 3. 真实薪资区间 */}
console.log('全部')} />
{salaryData.ranges.map((range, index) => (
{range.experience}
{range.min}-{range.max}K
))}
{salaryData.dataSource}
{/* 4. 员工评价 */}
{reviews.map((review) => (
{review.userName}
{review.userRole}
{review.time}
{review.content}
{review.tags.map((tag) => (
{tag}
))}
))}
{/* 5. 评论输入框(固定底部) */}
console.log('发布', comment)}>
发布
);
}
// 跨平台阴影
const shadow = {
shadowColor: colors.textPrimary,
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.06,
shadowRadius: 8,
elevation: 3,
};
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: colors.pageBg,
},
stateWrap: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
stateText: {
marginTop: 12,
fontSize: 14,
color: colors.textSecondary,
},
retryBtn: {
marginTop: 16,
paddingHorizontal: 24,
paddingVertical: 8,
borderRadius: 18,
backgroundColor: colors.primaryBtn,
},
retryText: {
fontSize: 13,
fontWeight: '600',
color: colors.white,
},
/* ---- 顶部导航 ---- */
header: {
height: 48,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
headerTitle: {
fontSize: 17,
fontWeight: '700',
color: colors.textPrimary,
},
headerBtn: {
position: 'absolute',
top: 0,
width: 48,
height: 48,
alignItems: 'center',
justifyContent: 'center',
},
scroll: {
flex: 1,
},
content: {
paddingHorizontal: 20,
paddingBottom: 24,
},
card: {
backgroundColor: colors.white,
borderRadius: 16,
padding: 16,
marginBottom: 16,
...shadow,
},
/* ---- 基本信息 ---- */
basicTop: {
flexDirection: 'row',
alignItems: 'flex-start',
justifyContent: 'space-between',
},
positionName: {
flex: 1,
fontSize: 22,
fontWeight: '700',
color: colors.textPrimary,
},
statusCol: {
alignItems: 'flex-end',
marginLeft: 12,
},
statusTag: {
backgroundColor: colors.successLight,
paddingHorizontal: 8,
paddingVertical: 3,
borderRadius: 10,
},
statusText: {
fontSize: 12,
fontWeight: '600',
color: colors.successText,
},
viewsText: {
marginTop: 6,
fontSize: 12,
color: colors.textHint,
},
salary: {
marginTop: 14,
fontSize: 20,
fontWeight: '700',
color: colors.primary,
},
metaText: {
marginTop: 8,
fontSize: 13,
color: colors.textSecondary,
},
benefits: {
flexDirection: 'row',
flexWrap: 'wrap',
marginTop: 12,
},
benefitTag: {
backgroundColor: colors.primaryLight,
borderWidth: 1,
borderColor: colors.primaryBorder,
borderRadius: 12,
paddingHorizontal: 10,
paddingVertical: 4,
marginRight: 8,
marginBottom: 8,
},
benefitText: {
fontSize: 12,
color: colors.primary,
},
/* ---- 分区标题 ---- */
sectionHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 12,
},
sectionBar: {
width: 4,
height: 16,
borderRadius: 2,
backgroundColor: colors.primary,
marginRight: 8,
},
sectionTitle: {
flex: 1,
fontSize: 17,
fontWeight: '700',
color: colors.textPrimary,
},
sectionLinkBtn: {
paddingLeft: 8,
},
sectionLink: {
fontSize: 13,
color: colors.primary,
},
/* ---- 综合评分 ---- */
scoreRow: {
flexDirection: 'row',
alignItems: 'center',
},
scoreInfo: {
flex: 1,
alignItems: 'flex-start',
marginLeft: 8,
},
scoreNumberRow: {
flexDirection: 'row',
alignItems: 'baseline',
},
scoreNumber: {
fontSize: 40,
fontWeight: '700',
color: colors.primary,
},
scoreUnit: {
fontSize: 14,
fontWeight: '600',
color: colors.primary,
},
levelTag: {
marginTop: 4,
backgroundColor: colors.successLight,
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 12,
},
levelTagText: {
fontSize: 13,
fontWeight: '600',
color: colors.successText,
},
scoreRank: {
marginTop: 8,
fontSize: 12,
color: colors.textSecondary,
},
compareBtn: {
flexDirection: 'row',
alignItems: 'center',
alignSelf: 'flex-start',
marginTop: 16,
},
compareText: {
marginLeft: 4,
fontSize: 14,
fontWeight: '600',
color: colors.primary,
},
/* ---- 薪资区间 ---- */
salaryRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingVertical: 12,
borderBottomWidth: 1,
borderBottomColor: colors.borderLight,
},
salaryRowLast: {
borderBottomWidth: 0,
},
salaryExp: {
fontSize: 14,
color: colors.textSecondary,
},
salaryRange: {
fontSize: 15,
fontWeight: '700',
color: colors.textPrimary,
},
salarySource: {
marginTop: 12,
fontSize: 12,
color: colors.textHint,
},
/* ---- 员工评价 ---- */
reviewHeader: {
flexDirection: 'row',
alignItems: 'center',
},
reviewName: {
fontSize: 14,
fontWeight: '700',
color: colors.textPrimary,
},
reviewRole: {
marginLeft: 8,
fontSize: 12,
color: colors.textSecondary,
},
reviewTime: {
flex: 1,
textAlign: 'right',
fontSize: 12,
color: colors.textHint,
},
reviewContent: {
marginTop: 10,
fontSize: 13,
lineHeight: 20,
color: colors.textPrimary,
},
reviewTags: {
flexDirection: 'row',
flexWrap: 'wrap',
marginTop: 10,
},
reviewTag: {
backgroundColor: colors.pageBg,
borderWidth: 1,
borderColor: colors.border,
borderRadius: 12,
paddingHorizontal: 10,
paddingVertical: 4,
marginRight: 8,
marginBottom: 8,
},
reviewTagText: {
fontSize: 12,
color: colors.textSecondary,
},
/* ---- 评论输入框 ---- */
commentBar: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 16,
paddingVertical: 10,
backgroundColor: colors.white,
borderTopWidth: 1,
borderTopColor: colors.borderLight,
},
commentInput: {
flex: 1,
height: 40,
borderRadius: 20,
backgroundColor: colors.pageBg,
paddingHorizontal: 16,
fontSize: 14,
color: colors.textPrimary,
},
publishBtn: {
marginLeft: 12,
paddingHorizontal: 18,
height: 40,
borderRadius: 20,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: colors.primaryBtn,
},
publishText: {
fontSize: 14,
fontWeight: '600',
color: colors.white,
},
});